package edu.kit.csl.pisa.training;

/*
This file is part of the PISA Alignment Tool.

Copyright (C) 2013
Karlsruhe Institute of Technology
Cognitive Systems Lab (CSL)
Felix Stahlberg

PISA is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

PISA is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with PISA. If not, see <http://www.gnu.org/licenses/>.
*/

import java.util.LinkedList;
import java.util.List;

import edu.kit.csl.pisa.datatypes.BadParameterValueException;
import edu.kit.csl.pisa.datatypes.SentencePair;
import edu.kit.csl.pisa.io.Logger;
import edu.kit.csl.pisa.models.AlignmentModel;
import edu.kit.csl.pisa.training.eva2.AlignmentProblem;
import edu.kit.csl.pisa.training.eva2.CrossoverLeftRight;
import edu.kit.csl.pisa.training.eva2.IndividualSentenceAlignment;
import edu.kit.csl.pisa.training.eva2.MutateAlignment;
import edu.kit.csl.pisa.training.eva2.MutateInsertWordBoundary;
import edu.kit.csl.pisa.training.eva2.MutateMoveWordBoundary;
import edu.kit.csl.pisa.training.eva2.MutateRealign;
import edu.kit.csl.pisa.training.eva2.MutateRemoveWordBoundary;
import edu.kit.csl.pisa.ui.Configuration;
import eva2.OptimizerFactory;
import eva2.server.go.individuals.AbstractEAIndividual;
import eva2.server.go.operators.crossover.InterfaceCrossover;
import eva2.server.go.operators.mutation.InterfaceMutation;
import eva2.server.go.operators.mutation.MutateEAMixer;
import eva2.server.go.operators.terminators.EvaluationTerminator;
import eva2.server.go.populations.Population;
import eva2.server.modules.GOParameters;

/**
 * This class provides the adapter (GoF) class to the EvA2 optimization
 * library. Supplementary classes for convincing EvA2 to deal with alignments
 * are provided in the package {@link edu.kit.csl.pisa.training.eva2}. EvA2
 * configuration parameters (i.e. the chosen optimization method) are
 * accessible via the {@link edu.kit.csl.ui.Configuration} interface of PISA.
 * 
 * @see http://www.ra.cs.uni-tuebingen.de/software/EvA2/index.html
 * @see Configuration
 */
public class Eva2MaximizationStrategy extends MaximizationStrategy {

	/*
	 * Optimization ID for the optimization factory.
	 */
	private int optId;
	
	/*
	 * Used mutator.
	 */
	private InterfaceMutation mutOp;
	private final float mutProb;

	/*
	 * Used crossover operator.
	 */
	private InterfaceCrossover coOp;
	private final float coProb;
	
	/*
	 * Some configuration parameters
	 */
	private final int nIterations;
	
	/*
	 * Population size
	 */
	private final int popSize;
	
	/**
	 * Sole constructor. Initializes the optimization problem (not the
	 * population, of course) and transfers configuration parameter to EvA2.
	 * 
	 * @param model Alignment model for which the alignments should be
	 * 			optimized
	 * @throws BadParameterValueException if the optimization algorithm is
	 * 				unknown
	 */
	public Eva2MaximizationStrategy(AlignmentModel model)
			throws BadParameterValueException {
		super(model);
		optId = -1;
		String optName = model.getConfigString("MaxAlgorithm");
		try {
			optId = OptimizerFactory.class.getDeclaredField(optName)
					.getInt(null);
			
		} catch (Exception e) {
			if (optName.equals("SIMANN")) {
				;
			} else {
				throw new BadParameterValueException("Unknown MaxAlgorithm");
			}
		}
		// Set up cross-over and mutation operators
		List<MutateAlignment> mutators = new LinkedList<MutateAlignment>();
		if (1 == model.getConfigInteger("MutateIns")) {
			mutators.add(new MutateInsertWordBoundary());
		}
		if (1 == model.getConfigInteger("MutateDel")) {
			mutators.add(new MutateRemoveWordBoundary());
		}
		if (1 == model.getConfigInteger("MutateMove")) {
			mutators.add(new MutateMoveWordBoundary());
		}
		if (1 == model.getConfigInteger("MutateRealign")) {
			mutators.add(new MutateRealign());
		}
		mutOp = new MutateEAMixer(mutators.toArray(new MutateAlignment[]{}));
		mutProb = model.getConfigFloat("MutationProb");
		coOp = new CrossoverLeftRight();
		coProb = model.getConfigFloat("CrossoverProb");
		nIterations = model.getConfigInteger("MaxAlgorithmIterations");
		popSize = model.getConfigInteger("MaxAlgorithmPopulationSize");
		if (popSize < 1) {
			throw new BadParameterValueException("Population is too small.");
		}
	}

	/**
	 * This method maximizes the alignment score for the given sentence pair.
	 * It delegates the task to EvA2 via the interface provided by the
	 * <code>OptimizerFactory</code>.
	 * 
	 * @param pair the sentence pair for which optimization should be done
	 * @see eva2.OptimizerFactory
	 */
	@Override
	public void maximize(SentencePair pair)
			throws UnsupportedOperationException {
		GOParameters params;
		AlignmentProblem problem = new AlignmentProblem(model, pair, popSize);
		AbstractEAIndividual.setOperators(problem.getIndividualTemplate(),
				mutOp, mutProb, coOp, coProb);
		params = OptimizerFactory.getParams(optId, problem);
		params.setTerminator(new EvaluationTerminator(nIterations));
		Population pop = OptimizerFactory.optimizeToPop(params, null);
		if (pop == null || pop.size() == 0) {
			Logger.getSingleton().error("EvA2 optimization run failed for "
				+ pair);
			return;
		}
		pair.removeAllAlignments();
		for (Object o : pop) {
			IndividualSentenceAlignment indy = (IndividualSentenceAlignment) o;
			pair.addAlignment(indy.getSentenceAlignment().getAlignment(),
					-indy.getFitness(0));
		}
	}
}
